import Fuse from 'fuse.js'
import type { 
  SearchResult, 
  ContentSearchResult, 
  SearchOptions, 
  ContentSearchOptions,
  SearchCriteria,
  ContentMatch,
  HistoryVersion,
  VersionEntries
} from '../types/basic'
import type { HistoryParser } from '../types/interfaces'
import { ErrorType, type AppError } from '../types/errors'

interface IndexedFile {
  versionId: string
  fileId: string
  fileName: string
  originalPath: string
  timestamp: number
  size: number
  content?: string
  isTextFile: boolean
}

interface SearchIndex {
  files: IndexedFile[]
  fileNameFuse: Fuse<IndexedFile>
  contentFuse: Fuse<IndexedFile>
  lastBuilt: number
}

export class SearchEngineImpl {
  private historyParser: HistoryParser
  // Removed unused _fileOperations property
  private searchIndex: SearchIndex | null = null
  private isBuilding = false

  // Text file extensions for content indexing
  private readonly textExtensions = new Set([
    '.txt', '.md', '.js', '.ts', '.jsx', '.tsx', '.vue', '.html', '.htm', 
    '.css', '.scss', '.sass', '.less', '.json', '.xml', '.yaml', '.yml',
    '.py', '.java', '.c', '.cpp', '.h', '.hpp', '.cs', '.php', '.rb',
    '.go', '.rs', '.swift', '.kt', '.scala', '.sh', '.bat', '.ps1',
    '.sql', '.r', '.m', '.pl', '.lua', '.dart', '.elm', '.ex', '.exs'
  ])

  constructor(historyParser: HistoryParser) {
    this.historyParser = historyParser
  }

  async buildIndex(): Promise<void> {
    if (this.isBuilding) {
      throw this.createError(ErrorType.OPERATION_IN_PROGRESS, 'Index is already being built')
    }

    this.isBuilding = true
    
    try {
      console.log('Building search index...')
      const startTime = Date.now()
      
      // Get all history versions
      const versions = await this.historyParser.scanHistoryVersions()
      const indexedFiles: IndexedFile[] = []

      // Process each version
      for (const version of versions) {
        try {
          await this.indexVersion(version, indexedFiles)
        } catch (error) {
          console.warn(`Failed to index version ${version.id}:`, error)
          // Continue with other versions
        }
      }

      // Create Fuse.js instances for different search types
      const fileNameFuse = new Fuse(indexedFiles, {
        keys: ['fileName', 'originalPath'],
        threshold: 0.3,
        includeScore: true,
        minMatchCharLength: 1
      })

      const contentFiles = indexedFiles.filter(f => f.isTextFile && f.content)
      const contentFuse = new Fuse(contentFiles, {
        keys: ['content'],
        threshold: 0.4,
        includeScore: true,
        minMatchCharLength: 2
      })

      this.searchIndex = {
        files: indexedFiles,
        fileNameFuse,
        contentFuse,
        lastBuilt: Date.now()
      }

      const duration = Date.now() - startTime
      console.log(`Search index built successfully in ${duration}ms. Indexed ${indexedFiles.length} files.`)
      
    } finally {
      this.isBuilding = false
    }
  }

  async searchByFileName(query: string, options: SearchOptions): Promise<SearchResult[]> {
    await this.ensureIndexBuilt()
    
    if (!query.trim()) {
      return []
    }

    const { fileNameFuse, files } = this.searchIndex!
    let results: SearchResult[]

    if (options.exactMatch) {
      // Exact match search
      const filtered = files.filter(file => {
        const fileName = options.caseSensitive ? file.fileName : file.fileName.toLowerCase()
        const searchQuery = options.caseSensitive ? query : query.toLowerCase()
        return fileName === searchQuery || file.originalPath.includes(searchQuery)
      })
      
      results = filtered.map(file => this.createSearchResult(file, 1.0))
    } else {
      // Fuzzy search using Fuse.js
      const fuseResults = fileNameFuse.search(query)
      results = fuseResults.map(result => 
        this.createSearchResult(result.item, 1 - (result.score || 0))
      )
    }

    // Apply filters
    results = this.applySearchFilters(results, options)

    // Sort by match score (highest first) and then by timestamp (newest first)
    results.sort((a, b) => {
      if (Math.abs(a.matchScore - b.matchScore) > 0.01) {
        return b.matchScore - a.matchScore
      }
      return b.timestamp - a.timestamp
    })

    // Limit results
    return results.slice(0, options.maxResults)
  }

  async searchByContent(query: string, options: ContentSearchOptions): Promise<ContentSearchResult[]> {
    await this.ensureIndexBuilt()
    
    if (!query.trim()) {
      return []
    }

    const { contentFuse, files } = this.searchIndex!
    const textFiles = files.filter(f => f.isTextFile && f.content)
    let results: ContentSearchResult[] = []

    if (options.regex) {
      // Regex search
      try {
        const regex = new RegExp(query, options.caseSensitive ? 'g' : 'gi')
        results = this.searchContentWithRegex(textFiles, regex, options)
      } catch (error) {
        throw this.createError(ErrorType.VALIDATION_ERROR, `Invalid regex pattern: ${query}`)
      }
    } else if (options.exactMatch) {
      // Exact content search
      results = this.searchContentExact(textFiles, query, options)
    } else {
      // Fuzzy content search using Fuse.js
      const fuseResults = contentFuse.search(query)
      results = await Promise.all(
        fuseResults.map(async result => {
          const matches = await this.findContentMatches(result.item.content!, query, options)
          return {
            ...this.createSearchResult(result.item, 1 - (result.score || 0)),
            matches
          }
        })
      )
    }

    // Apply filters
    const filteredResults = results.filter(result => 
      this.matchesSearchFilters(result, options)
    )

    // Sort by match score and timestamp
    filteredResults.sort((a, b) => {
      if (Math.abs(a.matchScore - b.matchScore) > 0.01) {
        return b.matchScore - a.matchScore
      }
      return b.timestamp - a.timestamp
    })

    return filteredResults.slice(0, options.maxResults)
  }

  async advancedSearch(criteria: SearchCriteria): Promise<SearchResult[]> {
    await this.ensureIndexBuilt()
    
    const { files } = this.searchIndex!
    let results: SearchResult[] = []

    // Start with all files
    let candidateFiles = [...files]

    // Apply file name filter
    if (criteria.fileName) {
      const nameOptions: SearchOptions = {
        caseSensitive: false,
        exactMatch: false,
        fileTypes: criteria.fileType ? [criteria.fileType] : [],
        versionIds: criteria.versionIds || [],
        maxResults: 1000
      }
      const nameResults = await this.searchByFileName(criteria.fileName, nameOptions)
      const nameFileIds = new Set(nameResults.map(r => `${r.versionId}:${r.fileId}`))
      candidateFiles = candidateFiles.filter(f => nameFileIds.has(`${f.versionId}:${f.fileId}`))
    }

    // Apply content filter
    if (criteria.content) {
      const contentOptions: ContentSearchOptions = {
        caseSensitive: false,
        exactMatch: false,
        regex: false,
        contextLines: 1,
        fileTypes: criteria.fileType ? [criteria.fileType] : [],
        versionIds: criteria.versionIds || [],
        maxResults: 1000
      }
      const contentResults = await this.searchByContent(criteria.content, contentOptions)
      const contentFileIds = new Set(contentResults.map(r => `${r.versionId}:${r.fileId}`))
      candidateFiles = candidateFiles.filter(f => contentFileIds.has(`${f.versionId}:${f.fileId}`))
    }

    // Apply other filters
    candidateFiles = candidateFiles.filter(file => {
      // File type filter
      if (criteria.fileType) {
        const ext = this.getFileExtension(file.fileName)
        if (ext !== criteria.fileType) return false
      }

      // Date range filter
      if (criteria.dateRange) {
        const fileDate = new Date(file.timestamp)
        if (fileDate < criteria.dateRange.start || fileDate > criteria.dateRange.end) {
          return false
        }
      }

      // Size range filter
      if (criteria.sizeRange) {
        if (file.size < criteria.sizeRange.min || file.size > criteria.sizeRange.max) {
          return false
        }
      }

      // Version IDs filter
      if (criteria.versionIds && criteria.versionIds.length > 0) {
        if (!criteria.versionIds.includes(file.versionId)) return false
      }

      return true
    })

    // Convert to search results with calculated scores
    results = candidateFiles.map(file => {
      let score = 0.5 // Base score

      // Boost score based on criteria matches
      if (criteria.fileName && file.fileName.toLowerCase().includes(criteria.fileName.toLowerCase())) {
        score += 0.3
      }
      if (criteria.content && file.content?.toLowerCase().includes(criteria.content.toLowerCase())) {
        score += 0.2
      }

      return this.createSearchResult(file, Math.min(score, 1.0))
    })

    // Sort by score and timestamp
    results.sort((a, b) => {
      if (Math.abs(a.matchScore - b.matchScore) > 0.01) {
        return b.matchScore - a.matchScore
      }
      return b.timestamp - a.timestamp
    })

    return results.slice(0, 100) // Limit advanced search results
  }

  // Clear the search index
  clearIndex(): void {
    this.searchIndex = null
  }

  // Get index statistics
  getIndexStats() {
    if (!this.searchIndex) {
      return { indexed: false }
    }

    const { files, lastBuilt } = this.searchIndex
    const textFiles = files.filter(f => f.isTextFile).length
    const totalSize = files.reduce((sum, f) => sum + f.size, 0)

    return {
      indexed: true,
      totalFiles: files.length,
      textFiles,
      totalSize,
      lastBuilt: new Date(lastBuilt),
      versions: new Set(files.map(f => f.versionId)).size
    }
  }

  private async indexVersion(version: HistoryVersion, indexedFiles: IndexedFile[]): Promise<void> {
    if (!version.hasEntriesFile) {
      // Try to index files without entries.json
      await this.indexVersionWithoutEntries(version, indexedFiles)
      return
    }

    try {
      const entries = await this.historyParser.parseVersionEntries(version.id)
      await this.indexVersionWithEntries(version, entries, indexedFiles)
    } catch (error) {
      console.warn(`Failed to parse entries for version ${version.id}, trying without entries:`, error)
      await this.indexVersionWithoutEntries(version, indexedFiles)
    }
  }

  private async indexVersionWithEntries(
    version: HistoryVersion, 
    entries: VersionEntries, 
    indexedFiles: IndexedFile[]
  ): Promise<void> {
    for (const entry of entries.entries) {
      try {
        const stats = await this.historyParser.getFileStats(version.id, entry.id)
        if (!stats.exists) continue

        const fileName = entry.id
        const isTextFile = this.isTextFile(fileName)
        let content: string | undefined

        // Read content for text files (with size limit)
        if (isTextFile && stats.size < 1024 * 1024) { // 1MB limit
          try {
            content = await this.historyParser.getFileContent(version.id, entry.id)
          } catch (error) {
            console.warn(`Failed to read content for ${entry.id}:`, error)
          }
        }

        indexedFiles.push({
          versionId: version.id,
          fileId: entry.id,
          fileName,
          originalPath: entries.resource,
          timestamp: entry.timestamp,
          size: stats.size,
          content,
          isTextFile
        })
      } catch (error) {
        console.warn(`Failed to index file ${entry.id} in version ${version.id}:`, error)
      }
    }
  }

  private async indexVersionWithoutEntries(version: HistoryVersion, indexedFiles: IndexedFile[]): Promise<void> {
    try {
      const files = await this.historyParser.getVersionFiles(version.id)
      
      for (const fileName of files) {
        try {
          const stats = await this.historyParser.getFileStats(version.id, fileName)
          if (!stats.exists) continue

          const isTextFile = this.isTextFile(fileName)
          let content: string | undefined

          if (isTextFile && stats.size < 1024 * 1024) {
            try {
              content = await this.historyParser.getFileContent(version.id, fileName)
            } catch (error) {
              console.warn(`Failed to read content for ${fileName}:`, error)
            }
          }

          indexedFiles.push({
            versionId: version.id,
            fileId: fileName,
            fileName,
            originalPath: fileName, // Use filename as path when entries.json is missing
            timestamp: version.timestamp,
            size: stats.size,
            content,
            isTextFile
          })
        } catch (error) {
          console.warn(`Failed to index file ${fileName} in version ${version.id}:`, error)
        }
      }
    } catch (error) {
      console.warn(`Failed to list files in version ${version.id}:`, error)
    }
  }

  private async ensureIndexBuilt(): Promise<void> {
    if (!this.searchIndex) {
      await this.buildIndex()
    }
  }

  private isTextFile(fileName: string): boolean {
    const ext = this.getFileExtension(fileName)
    return this.textExtensions.has(ext)
  }

  private getFileExtension(fileName: string): string {
    const lastDot = fileName.lastIndexOf('.')
    return lastDot >= 0 ? fileName.substring(lastDot).toLowerCase() : ''
  }

  private createSearchResult(file: IndexedFile, matchScore: number): SearchResult {
    return {
      versionId: file.versionId,
      fileId: file.fileId,
      originalPath: file.originalPath,
      fileName: file.fileName,
      timestamp: file.timestamp,
      matchScore: Math.max(0, Math.min(1, matchScore))
    }
  }

  private applySearchFilters(results: SearchResult[], options: SearchOptions): SearchResult[] {
    return results.filter(result => this.matchesSearchFilters(result, options))
  }

  private matchesSearchFilters(result: SearchResult, options: SearchOptions): boolean {
    // File type filter
    if (options.fileTypes.length > 0) {
      const ext = this.getFileExtension(result.fileName).substring(1) // Remove the dot
      if (!options.fileTypes.includes(ext)) return false
    }

    // Version IDs filter
    if (options.versionIds.length > 0) {
      if (!options.versionIds.includes(result.versionId)) return false
    }

    return true
  }

  private searchContentWithRegex(
    files: IndexedFile[], 
    regex: RegExp, 
    options: ContentSearchOptions
  ): ContentSearchResult[] {
    const results: ContentSearchResult[] = []

    for (const file of files) {
      if (!file.content) continue

      const matches = this.findRegexMatches(file.content, regex, options)
      if (matches.length > 0) {
        results.push({
          ...this.createSearchResult(file, 0.8), // High score for regex matches
          matches
        })
      }
    }

    return results
  }

  private searchContentExact(
    files: IndexedFile[], 
    query: string, 
    options: ContentSearchOptions
  ): ContentSearchResult[] {
    const results: ContentSearchResult[] = []
    const searchQuery = options.caseSensitive ? query : query.toLowerCase()

    for (const file of files) {
      if (!file.content) continue

      const content = options.caseSensitive ? file.content : file.content.toLowerCase()
      if (content.includes(searchQuery)) {
        const matches = this.findExactMatches(file.content, query, options)
        results.push({
          ...this.createSearchResult(file, 0.9), // High score for exact matches
          matches
        })
      }
    }

    return results
  }

  private async findContentMatches(content: string, query: string, options: ContentSearchOptions): Promise<ContentMatch[]> {
    const matches: ContentMatch[] = []
    const lines = content.split('\n')
    const searchQuery = options.caseSensitive ? query : query.toLowerCase()

    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      const searchLine = options.caseSensitive ? line : line.toLowerCase()
      
      if (searchLine.includes(searchQuery)) {
        const matchStart = searchLine.indexOf(searchQuery)
        const matchEnd = matchStart + searchQuery.length

        matches.push({
          lineNumber: i + 1,
          lineContent: line,
          matchStart,
          matchEnd,
          context: this.getContextLines(lines, i, options.contextLines)
        })
      }
    }

    return matches
  }

  private findRegexMatches(content: string, regex: RegExp, options: ContentSearchOptions): ContentMatch[] {
    const matches: ContentMatch[] = []
    const lines = content.split('\n')

    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      const lineMatches = Array.from(line.matchAll(regex))
      
      for (const match of lineMatches) {
        matches.push({
          lineNumber: i + 1,
          lineContent: line,
          matchStart: match.index || 0,
          matchEnd: (match.index || 0) + match[0].length,
          context: this.getContextLines(lines, i, options.contextLines)
        })
      }
    }

    return matches
  }

  private findExactMatches(content: string, query: string, options: ContentSearchOptions): ContentMatch[] {
    const matches: ContentMatch[] = []
    const lines = content.split('\n')
    const searchQuery = options.caseSensitive ? query : query.toLowerCase()

    for (let i = 0; i < lines.length; i++) {
      const line = lines[i]
      const searchLine = options.caseSensitive ? line : line.toLowerCase()
      
      let startIndex = 0
      while (true) {
        const matchIndex = searchLine.indexOf(searchQuery, startIndex)
        if (matchIndex === -1) break

        matches.push({
          lineNumber: i + 1,
          lineContent: line,
          matchStart: matchIndex,
          matchEnd: matchIndex + query.length,
          context: this.getContextLines(lines, i, options.contextLines)
        })

        startIndex = matchIndex + 1
      }
    }

    return matches
  }

  private getContextLines(lines: string[], lineIndex: number, contextLines: number): string[] {
    const start = Math.max(0, lineIndex - contextLines)
    const end = Math.min(lines.length, lineIndex + contextLines + 1)
    return lines.slice(start, end)
  }

  private createError(type: ErrorType, message: string, details?: any): AppError {
    return {
      type,
      message,
      details,
      timestamp: new Date(),
      recoverable: type !== ErrorType.INVALID_HISTORY_FORMAT
    }
  }
}

import { historyParser } from './history-parser'

// Create default instance
export const searchEngine = new SearchEngineImpl(historyParser)

// Factory function
export function createSearchEngine(
  historyParserInstance?: HistoryParser
): SearchEngineImpl {
  return new SearchEngineImpl(
    historyParserInstance || historyParser
  )
}